/** @file tex_jpeg.cpp
    @brief JPEG images */

#include "tex/tex_jpeg.h"

using namespace G3;

//==============================================================================
// Constructors & Destructors
//==============================================================================
G3::JPG_ERROR_MANAGER::JPG_ERROR_MANAGER () {
    log = NULL;
}

//==============================================================================
// Meant to replace the standard error_exit method. Instead of the standard method,
// this one does not exit. It returns to the last jumppoint set.
//
//	A jumppoint can be set like this:
//
//	JPG_ERROR_MANAGER jerm;
//	int retval = setjmp (jerm.setjmp_buffer);
//	// We land here (retval = 1) if a fatal error ocurrs
//	if (retval == 1)
//		return NULL;
//
//	USAGE:: struct jpeg_decompress_struct::err->error_exit = G3::jpg_error_exit
//
//	NOTE:: In order to redirect error messages to a log, JPG_ERROR_MANAGER::log
//		   should be set to point to an existing G3::LOG class
//==============================================================================
void/*METHODDEF(void)*/ G3::jpg_error_exit (j_common_ptr cinfo) {
    JPG_ERROR_POINTER err = (JPG_ERROR_POINTER) cinfo->err;
    // Display error message
    (*cinfo->err->output_message) (cinfo);
    // Abort processing this jpeg
    jpeg_abort (cinfo);
    // Return control to the setjmp point with a return value of 1
    longjmp (err->setjmp_buffer, 1);
}

//==============================================================================
// This is used to override libJPEG default output_message.
//
//	When JPG_ERROR_MANAGER::log is set properly and the error manager is loaded
//	like this:
//
//	JPG_ERROR_MANAGER jerm;
//	struct jpeg_decompress_struct cinfo;
//	cinfo.err = jpeg_std_error (&jerm);
//
//	Then error messages are reported into the log instead of just stderr
//==============================================================================
void/*METHODDEF(void)*/ G3::jpg_output_message (j_common_ptr cinfo) {
    JPG_ERROR_POINTER err = (JPG_ERROR_POINTER) cinfo->err;
    char buffer [256];

    (*cinfo->err->format_message) (cinfo, buffer);

    if (err->log != NULL)
        err->log->Report ("ERROR! libJPEG: %s\n", buffer);
    else
        fprintf (stderr, "ERROR! libJPEG: %s\n", buffer);
}

//==============================================================================
// Initializes a stream source for libJPEG
//==============================================================================
void/*METHODDEF(void)*/ G3::init_source (j_decompress_ptr cinfo) {
    JPG_SOURCE_POINTER src = (JPG_SOURCE_POINTER) cinfo->src;

// We reset the empty-input-file flag for each image,
// but we don't clear the input buffer.
// This is correct behavior for reading a series of images from one source.
    src->file_started = true;
}

//==============================================================================
// Fills libJPEG input buffer
//==============================================================================
boolean/*METHODDEF(boolean)*/ G3::fill_input_buffer (j_decompress_ptr cinfo) {
    JPG_SOURCE_POINTER src = (JPG_SOURCE_POINTER) cinfo->src;

    size_t num_bytes = src->stream->Read (src->buffer, JPG_BUFFER_SIZE);
    if (num_bytes <= 0) {
        if (src->file_started)
            ERREXIT (cinfo, JERR_INPUT_EMPTY);
        WARNMS (cinfo, JWRN_JPEG_EOF);
        // Insert a fake EOI marker
        src->buffer [0] = (JOCTET) 0xFF;
        src->buffer [1] = (JOCTET) JPEG_EOI;
        num_bytes = 2;
    }

    src->pub.next_input_byte = src->buffer;
    src->pub.bytes_in_buffer = num_bytes;
    src->file_started = false;

    return TRUE;
}

//==============================================================================
// Skips num_bytes from the input stream source
//==============================================================================
void/*METHODDEF(void)*/ G3::skip_input_data (j_decompress_ptr cinfo, long num_bytes) {
    JPG_SOURCE_POINTER src = (JPG_SOURCE_POINTER) cinfo->src;

    if (num_bytes <= (long) src->pub.bytes_in_buffer) {
        src->pub.bytes_in_buffer -= num_bytes;
        src->pub.next_input_byte += num_bytes;
    } else {
        num_bytes -= src->pub.bytes_in_buffer;
        src->pub.bytes_in_buffer = 0;
        src->stream->Seek (num_bytes, STREAM::STREAM_MOVE);
    }
}

//==============================================================================
// Terminates the libJPEG stream source
//==============================================================================
void/*METHODDEF(void)*/ G3::term_source (j_decompress_ptr cinfo) {
    // nop
}

//==============================================================================
// Allows us to specify a filestream as a libJPEG source
//==============================================================================
GLOBAL(void) G3::jpeg_stream_src (j_decompress_ptr cinfo, STREAM *stream) {
    JPG_SOURCE_POINTER src;

// The source object and input buffer are made permanent so that a series
// of JPEG images can be read from the same file by calling jpeg_stdio_src
// only before the first one. (If we discarded the buffer at the end of
// one image, we'd likely lose the start of the next one.)
// This makes it unsafe to use this manager and a different source
// manager serially with the same JPEG object. Caveat programmer.

    // first time for this JPEG object?
    if (cinfo->src == NULL) {
        cinfo->src = (struct jpeg_source_mgr *)
                     (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof (JPG_SOURCE_MANAGER));
        src = (JPG_SOURCE_POINTER) cinfo->src;
    }

    src = (JPG_SOURCE_POINTER) cinfo->src;
    src->stream = stream;
    src->pub.init_source = init_source;
    src->pub.fill_input_buffer = fill_input_buffer;
    src->pub.skip_input_data = skip_input_data;
    // Use default method
    src->pub.resync_to_restart = jpeg_resync_to_restart;
    src->pub.term_source = term_source;
    // forces fill_input_buffer on first read
    src->pub.bytes_in_buffer = 0;
    // until buffer loaded
    src->pub.next_input_byte = NULL;
}

//==============================================================================
// Loads a JPG file from the Stream
//
//		FILESTREAM *Stream			Stream where to read the jpg file from
//
//		LOG *Log					Pointer to the log file where we report errors to
//
//	Returns a pointer to the loaded bitmap on success. Returns NULL on failure..
//==============================================================================
boost::shared_ptr<G3::BITMAP> G3::LoadJPG (STREAM *Stream, LOG *Log) {
    struct jpeg_decompress_struct cinfo;
    struct JPG_ERROR_MANAGER jerm;
    byte *jpeg_rgb = NULL;
    int retval = 0;

    Assert (Stream, "LoadJPG: Stream argument is a NULL pointer");

    boost::shared_ptr<G3::BITMAP> Bitmap (new G3::BITMAP);

    if (Bitmap == NULL) {
        if (Log != NULL)
            Log->Report ("ERROR! LoadJPG: Not enough memory for a new bitmap class (at least %ld more bytes needed)..\n", sizeof (BITMAP));
        return boost::shared_ptr<G3::BITMAP>();
    }

    retval = setjmp (jerm.setjmp_buffer);
    // We land here if a fatal error ocurrs
    if (retval == 1) {
        if (Bitmap->Data != NULL)
            delete [] Bitmap->Data;
        return boost::shared_ptr<G3::BITMAP>();
    }

    jerm.log = Log;
    cinfo.err = jpeg_std_error (&jerm);
    cinfo.err->error_exit = jpg_error_exit;
    cinfo.err->output_message = jpg_output_message;

    jpeg_create_decompress (&cinfo);

    jpeg_stream_src (&cinfo, Stream);
    jpeg_read_header (&cinfo, TRUE);
    jpeg_start_decompress (&cinfo);

    if (cinfo.out_color_space == JCS_GRAYSCALE) {
        Bitmap->Flags |= BITMAP_GRAYSCALE;
        Bitmap->BitsPerPixel = 8;

        Bitmap->Data = new byte [cinfo.image_width * cinfo.image_height];
    } else if (cinfo.out_color_space == JCS_RGB) {
        Bitmap->BitsPerPixel = 24;

        Bitmap->Data = new byte [3 * cinfo.image_width * cinfo.image_height];
    } else {
        if (Log != NULL)
            Log->Report ("ERROR! LoadJPG: Unsupported colorspace (%d)..\n", cinfo.out_color_space);

        jpeg_finish_decompress (&cinfo);
        jpeg_destroy_decompress (&cinfo);

        return boost::shared_ptr<G3::BITMAP>();
    }

    if (Bitmap->Data == NULL) {
        if (Log != NULL)
            Log->Report ("ERROR! LoadJPG: Not enough memory for storing decompressed jpg (at least %ld more bytes needed)..\n", 3 * cinfo.image_width * cinfo.image_height);

        jpeg_finish_decompress (&cinfo);
        jpeg_destroy_decompress (&cinfo);

        return boost::shared_ptr<G3::BITMAP>();
    }

    Bitmap->Width = cinfo.image_width;
    Bitmap->Height = cinfo.image_height;

    Bitmap->Size = Bitmap->BitsPerPixel * Bitmap->Width * Bitmap->Height / 8;

    while (cinfo.output_scanline < cinfo.output_height) {
        jpeg_rgb = Bitmap->Data + Bitmap->BitsPerPixel * cinfo.image_width * cinfo.output_scanline / 8;
        jpeg_read_scanlines (&cinfo, &jpeg_rgb, 1);
    }

    jpeg_finish_decompress (&cinfo);
    jpeg_destroy_decompress (&cinfo);

    // Mark the start of the next file
    Stream->Flush ();
    //Stream->SetFileStart ();

    return Bitmap;
}
/*
bool G3::SaveJPG (BITMAP *Bitmap, FILESTREAM *Stream, LOG *Log) {
    struct jpeg_decompress_struct cinfo;
    struct JPG_ERROR_MANAGER jerm;
    JSAMPROW row_pointer [1];
    int retval = 0;

    if (!Bitmap) {
        MainLog.Report ("SaveJPG: Invalid Bitmap pointer..\n");
        return false;
    }

    if (Width < 1 || Height < 1) {
        MainLog.Report ("SaveJPG: Invalid Resolution..\n");
        return false;
    }

    if (Bpp < 16) {
        MainLog.Report ("SaveJPG: Unsupported Color Depth..\n");
        return false;
    }

    retval = setjmp (jerm.setjmp_buffer);
    // We land here if a fatal error ocurrs
    if (retval == 1) {
        if (Bitmap->Data != NULL)
            delete [] Bitmap->Data;
        delete Bitmap;
        return NULL;
    }

    jerm.log = Log;
    cinfo.err = jpeg_std_error (&jerm);
    cinfo.err->error_exit = jpg_error_exit;
    cinfo.err->output_message = jpg_output_message;

    jpeg_create_compress (&cinfo);

    jpeg_stream_src (&cinfo, Stream);
    jpeg_read_header (&cinfo, TRUE);
    jpeg_start_decompress (&cinfo);

    FILE *f = fopen (fname, "wb");
    if (f) {
        jpeg_stdio_dest (&cinfo, f);

        cinfo.image_width = Width;
        cinfo.image_height = Height;

        cinfo.input_components = 3;
        cinfo.in_color_space = JCS_RGB;

        jpeg_set_defaults (&cinfo);
        jpeg_set_quality (&cinfo, 100, true);

        jpeg_start_compress (&cinfo, true);

        while (cinfo.next_scanline < cinfo.image_height) {
            row_pointer [0] = &Data [cinfo.next_scanline * Width * 3];
            jpeg_write_scanlines (&cinfo, row_pointer, 1);
        }

        jpeg_finish_compress (&cinfo);

        fclose (f);
        jpeg_destroy_compress (&cinfo);
        return true;
    }

    gMainLog.Report ("ERR1 :Failed to open JPG texture file \"%s\" for writing binary..\n", fname);

    jpeg_destroy_compress (&cinfo);

    return false;
}
*/
